/**
 * Live GPS Tracking Application
 * Interactive map widget
 *
 * Copyright 2009, Ivar Clemens
 */

package nl.ivarclemens.gpstrack.ui;

import nl.ivarclemens.gpstrack.tile.TileLoader;
import nl.ivarclemens.gpstrack.nmea.PositionEvent;
import nl.ivarclemens.gpstrack.track.MemTrack;
import nl.ivarclemens.gpstrack.track.TrackList;

import javax.microedition.lcdui.*;
import java.util.Vector;
import java.util.Enumeration;

public class MapWidget extends CustomItem implements PositionEvent {

    public static final int CENTRE_FIXED = 0;
    public static final int CENTRE_LATLON = 1;

    // Map
    TileLoader tileLoader = new TileLoader();
    int centreMode = 0;
    double centreLat;
    double centreLon;
    int zoom;

    // Parent
    Canvas parent = null;

    // Track
    private Vector tracks = new Vector();

    public MapWidget()
    {
        super("Map");
    }

    public void setCanvas(Canvas canvas)
    {
        parent = canvas;
    }

    public void startTransmission()
    {
        tileLoader.startTransmission();
    }

    public void stopTransmission()
    {
        tileLoader.stopTransmission();
    }

    public boolean mayTransmit()
    {
        return tileLoader.mayTransmit();
    }

    public void invalidateMap()
    {
        if(parent != null)
            parent.repaint();
        else
            this.repaint();
    }

    public void addTrack(MemTrack track)
    {
        tracks.addElement(track);
        invalidateMap();
    }

    public void addTrackList(TrackList trackList)
    {
        // Draw tracks
        Enumeration en = trackList.elements();

        // FIXME: Should cache tile-coordinates!
        while(en.hasMoreElements()) {
            MemTrack obj = (MemTrack) en.nextElement();
            tracks.addElement(obj);
        }

        invalidateMap();
    }

    public void setCentreMode(int mode)
    {
        centreMode = mode;
    }

    public void setCentre(double lat, double lon)
    {
        this.centreLat = lat;
        this.centreLon = lon;
        invalidateMap();
    }

    public void setZoomLevel(int zoom)
    {
        this.zoom = zoom;
        invalidateMap();
    }

    public void zoomOut()
    {
        if(this.zoom > 0)
            this.zoom--;
        invalidateMap();
    }

    public void zoomIn()
    {
        if(this.zoom <= 14)
            this.zoom++;
        invalidateMap();
    }

    /**
     * Converts longitude to tile coordinate
     */
    private double lonToTileX(double lon, int zoom)
    {
        return (lon + 180) / 360 * (1 << zoom);
    }

    /**
     * Converts lattitude to tile coordinate
     * 
     * FIXME: Really slow due to approx. log
     */
    private double latToTileY(double lat, int zoom)
    {
        double toLog = Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180);
        return (1 - nl.ivarclemens.Math.log(toLog) / Math.PI) / 2 * (1 << zoom);
    }

    private void drawCircle(Graphics g, int x, int y, int radius)
    {
        g.drawArc(x - radius, y - radius,
                radius * 2, radius * 2, 0, 360);
    }

    public void paint(Graphics g, int w, int h)
    {
        g.setColor(255, 255, 255);
        g.fillRect(0, 0, w, h);

        int centreX = (int) w / 2;
        int centreY = (int) h / 2;

        double tileX = this.lonToTileX(centreLon, zoom);
        double tileY = this.latToTileY(centreLat, zoom);

        int baseX = (int) Math.floor(tileX);
        int baseY = (int) Math.floor(tileY);
        
        int inTileX = (int) Math.floor(256 * (tileX - baseX));
        int inTileY = (int) Math.floor(256 * (tileY - baseY));

        for(int x = baseX - 1; x <= baseX + 1; x++) {
            for(int y = baseY - 1; y <= baseY + 1; y++) {
                int leftX = centreX - inTileX + 256 * (x - baseX);
                int topY = centreY - inTileY + 256 * (y - baseY);
                int rightX = leftX + 256;
                int bottomY = topY + 256;
                
                if(rightX < 0 || bottomY < 0 || topY > h || leftX > w)
                    continue;

                try {
                    Image img = tileLoader.getTile(x, y, zoom);
                    g.drawImage(img, leftX, topY, Graphics.TOP | Graphics.LEFT);
                } catch(Exception e) {
                    g.setColor(255, 255, 255);
                    g.fillRect(leftX, topY, 256, 256);
                }
            }
        }

        // Draw tracks
        Enumeration en = tracks.elements();

        // FIXME: Should cache tile-coordinates!
        while(en.hasMoreElements()) {
            MemTrack obj = (MemTrack) en.nextElement();

            // Skip track if it does not contain any points
            int len = obj.numPoints();

            if(len == 0)
                continue;

            // Convert bounding boxes to screen-coordinates
            int minLon = (int) (centreX + 256 * (this.lonToTileX(obj.getMinLon(), zoom) - tileX));
            int minLat = (int) (centreY + 256 * (this.latToTileY(obj.getMaxLat(), zoom) - tileY));
            int maxLon = (int) (centreX + 256 * (this.lonToTileX(obj.getMaxLon(), zoom) - tileX));
            int maxLat = (int) (centreY + 256 * (this.latToTileY(obj.getMinLat(), zoom) - tileY));

            // Skip tracks which are not on-screen
            if(maxLon < 0 || maxLat < 0 || minLon > w || minLat > h)
                continue;

            // Actually draw track
            int prevX = 0;
            int prevY = 0;

            for(int i = 0; i < len; i++)
            {
                g.setColor(0, 0, 196);
                int pntX = (int) (centreX + 256 * (this.lonToTileX(obj.getLon(i), zoom) - tileX));
                int pntY = (int) (centreY + 256 * (this.latToTileY(obj.getLat(i), zoom) - tileY));
                this.drawCircle(g, pntX, pntY, 1);

                if(i > 0) {
                    g.drawLine(prevX, prevY, pntX, pntY);
                    g.drawLine(prevX+1, prevY+1, pntX+1, pntY+1);
                }

                prevX = pntX;
                prevY = pntY;
            }

        }

        // Current position // FIXME: Does not have to be centre!
        g.setColor(0, 0, 0);
        drawCircle(g, centreX, centreY, 5);
    }

    public int getMinContentWidth()
    {
        return 100;
    }

    public int getMinContentHeight()
    {
        return 100;
    }

    public int getPrefContentWidth(int width)
    {
        return 200;
    }

    public int getPrefContentHeight(int height)
    {
        return 200;
    }

    public void posUpdate(long time, double lat, double lon, double ang, double vel)
    {
        if(centreMode == MapWidget.CENTRE_LATLON) {
            this.centreLat = lat;
            this.centreLon = lon;
            this.repaint();
        }
    }
}


